#include "FramelessWindow.h"

#include <QApplication>
#include <QDesktopWidget>
#include <QShowEvent>

#include <windows.h>
#include <WinUser.h>
#include <windowsx.h>
#include <dwmapi.h>
#include <objidl.h>
#include <gdiplus.h>
#include <QDebug>

#ifdef _MSC_VER
    #include <GdiPlusColor.h>
    #pragma comment (lib,"Dwmapi.lib")
    #pragma comment (lib,"user32.lib")
#endif

FramelessWindow::FramelessWindow(QWidget *parent)
    : QMainWindow(parent)
    , _resizable(true)
    , _justMaximized(false)
    , _titleBar(nullptr)
    , _borderWidth(5)
{
    setWindowFlags(windowFlags() | Qt::Window | Qt::FramelessWindowHint | Qt::WindowSystemMenuHint);
    setResizeable(_resizable);


}

void FramelessWindow::setResizeable(bool resizeable)
{
    bool visible = isVisible();
    _resizable = resizeable;
    if (_resizable){
        setWindowFlags(windowFlags() | Qt::WindowMaximizeButtonHint);
        HWND hwnd = (HWND)this->winId();
        DWORD style = ::GetWindowLong(hwnd, GWL_STYLE);
        ::SetWindowLong(hwnd, GWL_STYLE, style | WS_MAXIMIZEBOX | WS_THICKFRAME | WS_CAPTION);
    } else {
        setWindowFlags(windowFlags() & ~Qt::WindowMaximizeButtonHint);
        HWND hwnd = (HWND)this->winId();
        DWORD style = ::GetWindowLong(hwnd, GWL_STYLE);
        ::SetWindowLong(hwnd, GWL_STYLE, style & ~WS_MAXIMIZEBOX & ~WS_CAPTION);
    }

    const MARGINS shadow = { 1, 1, 1, 1 };
#ifdef _MSC_VER
    DwmExtendFrameIntoClientArea(HWND(winId()), &shadow);
#endif
    setVisible(visible);
}

void FramelessWindow::setResizeableAreaWidth(int width)
{
    if (width < 1) { width = 1; }
    _borderWidth = width;
}

void FramelessWindow::setContentsMargins(const QMargins &margins)
{
    QMainWindow::setContentsMargins(margins + _frames);
    _margins = margins;
}

void FramelessWindow::setContentsMargins(int left, int top, int right, int bottom)
{
    QMainWindow::setContentsMargins(
        left+_frames.left(), top + _frames.top(), right + _frames.right(), bottom + _frames.bottom()
    );
    _margins.setLeft(left);
    _margins.setTop(top);
    _margins.setRight(right);
    _margins.setBottom(bottom);
}

QMargins FramelessWindow::contentsMargins() const
{
    QMargins margins = QMainWindow::contentsMargins();
    margins -= _frames;
    return margins;
}

QRect FramelessWindow::contentsRect() const
{
    QRect rect = QMainWindow::contentsRect();
    int width = rect.width(), height = rect.height();
    rect.setLeft(rect.left() - _frames.left());
    rect.setTop(rect.top() - _frames.top());
    rect.setWidth(width);
    rect.setHeight(height);
    return rect;
}

void FramelessWindow::getContentsMargins(int *left, int *top, int *right, int *bottom) const
{
    QMargins margin = QMainWindow::contentsMargins();
    *left = margin.left(), *top = margin.top(), *right = margin.right(), *bottom = margin.bottom();
    if (!(left && top && right && bottom)) { return; }
    if (isMaximized()) {
        *left -= _frames.left(), *top -= _frames.top(), *right -= _frames.right(), *bottom -= _frames.bottom();
    }
}

void FramelessWindow::showFullScreen()
{
    if (isMaximized()) {
        QMainWindow::setContentsMargins(_margins);
        _frames = QMargins();
    }
    QMainWindow::showFullScreen();
}

void FramelessWindow::setTitleBar(QWidget *titleBar)
{
    _titleBar = titleBar;
    if (titleBar == nullptr) { return; }
    connect(titleBar, &QWidget::destroyed, this, &FramelessWindow::onTitleBarDestroyed);
}

void FramelessWindow::addIgnoreWidget(QWidget *widget)
{
    if (widget == nullptr) { return; }
    if (_whiteList.contains(widget)) { return; }
    _whiteList.append(widget);
}

void FramelessWindow::showEvent(QShowEvent *event)
{
    Q_UNUSED(event)
    if (!isMaximized()) {
        moveToCenter();
    }
}

bool FramelessWindow::nativeEvent(const QByteArray &eventType, void *message, long *result)
{
#if (QT_VERSION == QT_VERSION_CHECK(5, 11, 1))
    MSG* msg = *reinterpret_cast<MSG**>(message);
#else
    MSG* msg = reinterpret_cast<MSG*>(message);
#endif
    switch (msg->message) {
    case WM_NCCALCSIZE: {
        NCCALCSIZE_PARAMS& params = *reinterpret_cast<NCCALCSIZE_PARAMS*>(msg->lParam);
        if (params.rgrc[0].top != 0) { params.rgrc[0].top -= 1; }
        *result = WVR_REDRAW;
        return true;
    }
    case WM_NCHITTEST: {
        *result = 0;
        const LONG borderWidth = _borderWidth;
        long x = GET_X_LPARAM(msg->lParam);
        long y = GET_Y_LPARAM(msg->lParam);
        *result = getResizeRegion(x, y, borderWidth);
        if (*result != 0) { return true; }
        if (_titleBar == nullptr) { return false; }

        double dpr = this->devicePixelRatioF();
        QPoint pos = _titleBar->mapFromGlobal(QPoint(x / dpr,y / dpr));

        if (!_titleBar->rect().contains(pos)) { return false; }
        QWidget* child = _titleBar->childAt(pos);
        if (!child) {
            *result = HTCAPTION;
            return true;
        } else {
            if (_whiteList.contains(child)) {
                *result = HTCAPTION;
                return true;
            }
        }
        return false;
    }
    case WM_GETMINMAXINFO: {
        if (::IsZoomed(msg->hwnd)) {
            RECT frame = { 0, 0, 0, 0 };
            AdjustWindowRectEx(&frame, WS_OVERLAPPEDWINDOW, FALSE, 0);
            double dpr = this->devicePixelRatioF();
            _frames.setLeft(abs(frame.left)/dpr+0.5);
            _frames.setTop(abs(frame.bottom)/dpr+0.5);
            _frames.setRight(abs(frame.right)/dpr+0.5);
            _frames.setBottom(abs(frame.bottom)/dpr+0.5);
            QMainWindow::setContentsMargins(
                _frames.left() + _margins.left(), _frames.top() + _margins.top(),
                _frames.right() + _margins.right(), _frames.bottom() + _margins.bottom()
            );
            _justMaximized = true;
        } else {
            if (_justMaximized) {
                QMainWindow::setContentsMargins(_margins);
                _frames = QMargins();
                _justMaximized = false;
            }
        }
        return false;
    }
    default:
        return QMainWindow::nativeEvent(eventType, message, result);
    }
}

void FramelessWindow::onTitleBarDestroyed()
{
    if (_titleBar == QObject::sender()) {
        _titleBar = nullptr;
    }
}

long FramelessWindow::getResizeRegion(int x, int y, int borderWidth)
{
    RECT winrect;
    GetWindowRect(HWND(winId()), &winrect);
    long result = 0;
    if(_resizable) {
        bool resizeWidth = minimumWidth() != maximumWidth();
        bool resizeHeight = minimumHeight() != maximumHeight();
        if(resizeWidth) {
            if (x >= winrect.left && x < winrect.left + borderWidth) { result = HTLEFT; }
            if (x < winrect.right && x >= winrect.right - borderWidth) { result = HTRIGHT; }
        }
        if(resizeHeight) {
            if (y < winrect.bottom && y >= winrect.bottom - borderWidth) { result = HTBOTTOM; }
            if (y >= winrect.top && y < winrect.top + borderWidth) { result = HTTOP; }
        }
        if(resizeWidth && resizeHeight) {
            if (x >= winrect.left && x < winrect.left + borderWidth && y < winrect.bottom && y >= winrect.bottom - borderWidth) { result = HTBOTTOMLEFT; }
            if (x < winrect.right && x >= winrect.right - borderWidth && y < winrect.bottom && y >= winrect.bottom - borderWidth) { result = HTBOTTOMRIGHT; }
            if (x >= winrect.left && x < winrect.left + borderWidth && y >= winrect.top && y < winrect.top + borderWidth) { result = HTTOPLEFT; }
            if (x < winrect.right && x >= winrect.right - borderWidth && y >= winrect.top && y < winrect.top + borderWidth) { result = HTTOPRIGHT; }
        }
    }
    return result;
}

void FramelessWindow::moveToCenter()
{
    int parentWidth = (parentWidget() == nullptr) ? QApplication::desktop()->width() : parentWidget()->width();
    int parentHeight = (parentWidget() == nullptr) ? QApplication::desktop()->height() : parentWidget()->height();
    int windowWidth = width(), windowHeight = height();

    if (windowWidth > parentWidth) { windowWidth /= 2; }
    if (windowHeight > parentHeight) { windowHeight /= 2; }
    if (windowWidth > parentWidth || windowHeight > parentHeight) { resize(windowWidth, windowHeight); }

    int posX = (parentWidth - windowWidth) / 2;
    int posY = (parentHeight - windowHeight) / 2;

    QPoint pos(posX, posY);
    if (parentWidget() != nullptr) {
        QPoint globalPos = parentWidget()->mapToGlobal(QPoint(0, 0));
        pos = QPoint(posX + globalPos.x(), posY + globalPos.y());
    }
    //qDebug()<<posX<<posY;

    move(pos);
}
